/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/**
 *
 * \file
 * \brief Implementation for cross-platform utilities.
 * \author Antti Maula <antti.maula@tkk.fi>
 */
#include "ownutils.h"
#include <assert.h>
#include <time.h>
#include <limits.h>

//*****************************************************************************
//*****************************************************************************
#ifdef OWNMUTEX_DEBUGGING
FILE *ownmutex_dbg = NULL;
#endif
//*****************************************************************************
static long int owntime_begin_s = 0;
static long int owntime_begin_us = 0;
//*****************************************************************************

void ownSleep_ms(const ownTime_ms_t t_ms)
{
#if defined( WINDOWS_OS )
  Sleep(t_ms);
#elif defined( LINUX_OS )
  unsigned int v = t_ms * 1000;
  while(v > 1000000) {
    if (usleep(1000000)) perror("usleep() interrupted");
    v -= 1000000;
  }
  if (usleep(v)) perror("usleep() interrupted");
#else
 #warning Unsupported platform for function 'ownSleep_ms()'
#endif
}
//******************************************************************************

void ownSleep_us(const ownTime_ms_t t_us)
{
#if defined( WINDOWS_OS )
  // FIXME: Maybe emulate Microsecond precision using Sleep(0) and
  // absolute time checking?
  Sleep(t_us / 1000000); // Tuff luk :)
#elif defined( LINUX_OS )
  unsigned int v = t_us;
  while(v > 1000000) {
    usleep(1000000);
    v -= 1000000;
  }
  usleep(v);
#else
 #warning Unsupported platform for function 'ownSleep_us()'
#endif
}
//******************************************************************************

void ownRandom_init_time()
{
#if defined( WINDOWS_OS )
  //srandom(time(NULL));
#elif defined( LINUX_OS )
  long int s=0, us=0;
  ownTime_GetTimeOfDay(&s, &us);
  srandom(s+us);
#else
 #warning Unsupported platform for function 'ownSleep_us()'
#endif
}
//******************************************************************************

float ownRandom_get_range(const float min, const float max)
{
  assert(max > min);
  return min+((random() / (float)RAND_MAX)*(max-min));
}
//******************************************************************************

long int ownRandom_get_l_int(const long int min,
                             const long int max)
{
  assert(max > min);
  return min+(random()%(1+(max-min)));
}
//******************************************************************************

void ownTime_Reset(void)
{
  ownTime_GetTimeOfDay(&owntime_begin_s, &owntime_begin_us); 
}
//******************************************************************************

ownStatus ownTime_GetTimeOfDay(long int *seconds,long int *microseconds)
{
  ownStatus status = ownStatusOK;
  assert( seconds != NULL );
  assert( microseconds != NULL );

#if defined( WINDOWS_OS )
  FILETIME t;
  const LARGE_INTEGER *li = (const LARGE_INTEGER *)&t;
  GetSystemTimeAsFileTime(&t);
  __int64 v = (li->QuadPart - 0x19db1ded53e8000LL) / 10;
  *microseconds = v % 1000000;
  *seconds = v / 1000000;

#elif defined( LINUX_OS )
  struct timeval t;
  if (gettimeofday(&t,NULL) == 0) {
    *seconds = t.tv_sec;
    *microseconds = t.tv_usec;

  } else {
    status = ownStatusError;

  }

#else
# warning Unsupported platform for function 'ownTime_GetTimeOfDay'
#endif

  return status;
}
//******************************************************************************

double ownTime_get_s()
{
  double result;
#if defined( WINDOWS_OS )
  FILETIME t;
  const LARGE_INTEGER *li = (const LARGE_INTEGER *)&t;
  GetSystemTimeAsFileTime(&t);
  __int64 v = (li->QuadPart - 0x19db1ded53e8000LL) / 10;
  result = (v/1000000.00);

#elif defined( LINUX_OS )
  struct timeval t;
  if (gettimeofday(&t,NULL) == 0) {
    result = (t.tv_sec) + (t.tv_usec/1000000.0);
  } else {
    result = -1; // Negative time in case of error from gettimeofday.
  }

#else
 #warning Unsupported platform for function 'ownTime_get_s()'
#endif
  return result;
}
//******************************************************************************

double ownTime_get_s_since(const double since)
{
  return ownTime_get_s() - since;
}
//******************************************************************************

ownTime_ms_t ownTime_get_ms()
{
  ownTime_ms_t result = 0;
#if defined( WINDOWS_OS )
  result = GetTickCount();

#elif defined( LINUX_OS )
  struct timeval tv;
  const int r = gettimeofday(&tv, NULL);
  if (r) {
    perror("gettimeofday(&tv, NULL)");
    abort();
    
  } else {
    long int s_delta = tv.tv_sec - owntime_begin_s;
    long int us_delta = tv.tv_usec - owntime_begin_us;
    if (us_delta < 0) {
      s_delta -= 1;
      us_delta += 1000000L;
    }

    result = ((long long int)s_delta * 1000) + ((long long int)us_delta / 1000);
  }

#else
#warning Unsupported platform for function 'ownTime_get_ms()'
#endif

  return result;
}
//******************************************************************************

ownTime_ms_delta_t ownTime_get_ms_since(const ownTime_ms_t t_ms)
{
  const ownTime_ms_t now = ownTime_get_ms();
  const ownTime_ms_delta_t then = (now - t_ms);
  return then;
}
//******************************************************************************

ownTime_ms_delta_t ownTime_get_ms_left(const ownTime_ms_t limit,
                                       const ownTime_ms_t begin)
{
  return MAX(0, ( limit - ownTime_get_ms_since(begin) ));
}
//******************************************************************************

int ownStrsEqual(const char *strA, const char *strB)
{
  int value;
#if defined( WINDOWS_OS )
  value = (strcasecmp(strA, strB) == 0);
#elif defined( LINUX_OS )
  value = (strcasecmp(strA, strB) == 0);
#else
 #warning Unsupported platform for function 'ownStrsEqual'
#endif
  return value;
}
//******************************************************************************

ownSemHandle ownSemaphore_Init(int initial, int max)
{
  ownSemHandle handle;
#if defined( WINDOWS_OS )
  handle = CreateSemaphore(NULL, initial, max, NULL);

#elif defined( LINUX_OS )
  int sem_init_status;
  handle = (sem_t *)malloc(sizeof(sem_t));
  assert(handle != NULL);
  sem_init_status = sem_init(handle, 0, initial);
  if (sem_init_status) {
    perror("sem_init(handle, 0, initial)");
    abort();
  }

#else
 #warning Unsupported platform for function 'ownSemaphore_Init'
#endif

  return handle;
}
//******************************************************************************

ownStatus ownSemaphore_Destroy(ownSemHandle handle)
{
  ownStatus status = ownStatusOK;

#if defined( WINDOWS_OS )
 #warning Unsupported platform for function 'ownSemaphore_Destroy'
#elif defined( LINUX_OS )
  int r = sem_destroy(handle);
  if (r != 0) status = ownStatusError;

#else
 #warning Unsupported platform for function 'ownSemaphore_Destroy'
#endif

  return status;
}
//******************************************************************************

ownStatus ownSemaphore_Get(ownSemHandle handle, int timeout)
{
  ownStatus status;

#if defined( WINDOWS_OS )
  int r = WaitForSingleObject(handle, timeout);
  if (r == WAIT_OBJECT_0) {
    status = ownStatusOK;
  } else if (r == WAIT_TIMEOUT) {
    status = ownStatusTimeout;
  } else {
    status = ownStatusUnknown;
  }

#elif defined( LINUX_OS )
  int result;
  struct timespec t = { timeout*1000, (timeout%1000)*1000*1000};

  result = sem_timedwait(handle, &t);
  if (!result) {
    status = ownStatusOK;

  } else {
    if (errno == ETIMEDOUT)
      status = ownStatusTimeout;
    else
      status = ownStatusError;
  }

#else
 #warning Unsupported platform for function 'ownSemaphore_Get'
#endif

  return status;
}
//******************************************************************************

ownStatus ownSemaphore_Put(ownSemHandle handle, int count)
{
  ownStatus status = ownStatusOK;

#if defined( WINDOWS_OS )
  ReleaseSemaphore(handle, count, NULL);

#elif defined( LINUX_OS )
  int r = sem_post(handle);
  if (r != 0) status = ownStatusError;

#else
 #warning Unsupported platform for function 'ownSemaphore_Put'
#endif

  return status;
}
//******************************************************************************

#if defined( LINUX_OS )
// Incompatible source version; does not include this :)
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int kind);
#endif

ownMutexHandle ownMutex_Init(void)
{
  ownMutexHandle handle;

#if defined( WINDOWS_OS )
  handle = CreateMutex(NULL, FALSE, NULL);

#elif defined( LINUX_OS )
  int mutex_init_status;
  handle = malloc(sizeof(pthread_mutex_t));
  assert(handle != NULL);

  // Using error_checking mutexes for testing!
  pthread_mutexattr_t pmi;
  pthread_mutexattr_init(&pmi);
  pthread_mutexattr_settype(&pmi, PTHREAD_MUTEX_ERRORCHECK_NP);
  mutex_init_status = pthread_mutex_init(handle, &pmi);
  if (mutex_init_status) {
    perror("pthread_mutex_init(handle, &pmi)");
    abort();
  }

  // Fast mutex.
  //  mutex_init_status = pthread_mutex_init(handle, NULL);
  //  assert ( mutex_init_status == 0 );

#else
 #warning Unsupported platform for function 'ownMutex_Init'
#endif

  return handle;
}
//******************************************************************************

ownStatus ownMutex_Destroy(ownMutexHandle handle)
{
  ownStatus status = ownStatusOK;

#if defined( WINDOWS_OS )
  CloseHandle(handle);

#elif defined( LINUX_OS )
  const int r = pthread_mutex_destroy(handle);
  if (r == EBUSY) {
    fprintf(stderr,"Mutex %p is currently locked - cannot destroy!\n", handle);
    abort();
  }
  free(handle);

#else
 #warning Unsupported platform for function 'ownMutex_Destroy'
#endif

  return status;
}
//******************************************************************************

ownStatus ownMutex_TryLock(ownMutexHandle handle)
{
  ownStatus status = ownStatusOK;

#if defined( WINDOWS_OS )
#warning Currently Unimplemented for Windows - sorry.

#elif defined( LINUX_OS )
#ifdef OWNMUTEX_DEBUGGING
# warning OWNMUTEX_DEBUGGING enabled
  if (!ownmutex_dbg) ownmutex_dbg = fopen("ownmutex.log","a+");
  assert(ownmutex_dbg != NULL);
  fprintf(ownmutex_dbg,"Attempting ownMutex_TryLock(%p) by thread %x\n", handle, (unsigned int)pthread_self());
  fflush(ownmutex_dbg);
#endif
  const int r = pthread_mutex_trylock(handle);
  if (r) {
    if (r == EBUSY) {
      // Busy Mutex. Just return without locking.
      status = ownStatusBusy;
      
    } else {
      errno = r;
      perror("pthread_mutex_lock");
      abort();

    }
  }

#ifdef OWNMUTEX_DEBUGGING
  fprintf(ownmutex_dbg,"Acquired ownMutex_Lock(%p) by thread %x\n", handle, (unsigned int)pthread_self());
  fflush(ownmutex_dbg);
#endif

#else
 #warning Unsupported platform for function 'ownMutex_Lock'
#endif

  return status;
}
//******************************************************************************

ownStatus ownMutex_Lock(ownMutexHandle handle)
{
  ownStatus status = ownStatusOK;

#if defined( WINDOWS_OS )
  WaitForSingleObject(handle, INFINITE);

#elif defined( LINUX_OS )
#ifdef OWNMUTEX_DEBUGGING
# warning OWNMUTEX_DEBUGGING enabled
  if (!ownmutex_dbg) ownmutex_dbg = fopen("ownmutex.log","a+");
  assert(ownmutex_dbg != NULL);
  fprintf(ownmutex_dbg,"Attempting ownMutex_Lock(%p) by thread %x\n", handle, (unsigned int)pthread_self());
  fflush(ownmutex_dbg);
#endif
  const int r = pthread_mutex_lock(handle);
  if (r) {
    errno = r;
    perror("pthread_mutex_lock");
    abort();
  }

#ifdef OWNMUTEX_DEBUGGING
  fprintf(ownmutex_dbg,"Acquired ownMutex_Lock(%p) by thread %x\n", handle, (unsigned int)pthread_self());
  fflush(ownmutex_dbg);
#endif

#else
 #warning Unsupported platform for function 'ownMutex_Lock'
#endif

  return status;
}
//******************************************************************************

ownStatus ownMutex_Unlock(ownMutexHandle handle)
{
  ownStatus status = ownStatusOK;

#if defined( WINDOWS_OS )
  ReleaseMutex(handle);

#elif defined( LINUX_OS )
#ifdef OWNMUTEX_DEBUGGING
  assert(ownmutex_dbg != NULL);
  fprintf(ownmutex_dbg,"ownMutex_Unlock called (%p) by thread %x\n", handle, (unsigned int)pthread_self());
  fflush(ownmutex_dbg);
#endif
  const int r = pthread_mutex_unlock(handle);
  if (r) {
    if (r == EINVAL) {
      fprintf(stderr,"pthread_mutex_unlock(%p) = EINVAL\n", handle);
    } else if (r == EPERM) {
      fprintf(stderr,"pthread_mutex_unlock(%p) = EPERM\n", handle);
    }
    abort();
  }

#else
 #warning Unsupported platform for function 'ownMutex_Unlock'
#endif

  return status;
}
//******************************************************************************

void ownCond_Destroy(ownCondHandle handle)
{
#if defined( WINDOWS_OS )
  CloseHandle(handle);

#elif defined( LINUX_OS )

  pthread_cond_destroy(handle);
  free(handle);

#else
 #warning Unsupported platform for function 'ownCond_Init'
#endif
}
//******************************************************************************

ownCondHandle ownCond_Init(void)
{
  ownCondHandle handle;

#if defined( WINDOWS_OS )
  handle = CreateEvent(NULL, TRUE, FALSE, NULL);

#elif defined( LINUX_OS )
  handle = malloc(sizeof(pthread_cond_t));
  assert(handle != NULL);

  int r = pthread_cond_init(handle, NULL);
  if (r != 0) handle = NULL;
#else
 #warning Unsupported platform for function 'ownCond_Init'
#endif

  return handle;
}
//******************************************************************************

ownStatus ownCond_Wait(ownCondHandle handle, ownMutexHandle mutex, int timeout_ms)
{
  ownStatus status = ownStatusOK;

#if defined( WINDOWS_OS )
  int r;
  if (timeout_ms == -1) timeout_ms = INFINITE;
  /*
  ReleaseMutex(mutex);
  r = WaitForSingleObject(handle, timeout_ms);
  */
  // this function operates atomically, like it should
  r = SignalObjectAndWait(mutex, handle, timeout_ms, FALSE);
  WaitForSingleObject(mutex, INFINITE);
  if (r == WAIT_TIMEOUT) status = ownStatusTimeout;

#elif defined( LINUX_OS )
  int r;
  //printf("\n%lu Waiting for %p\n", pthread_self(), handle);
#ifdef OWNMUTEX_DEBUGGING
# warning OWNMUTEX_DEBUGGING enabled
  if (!ownmutex_dbg) ownmutex_dbg = fopen("ownmutex.log","a+");
  assert(ownmutex_dbg != NULL);
  fprintf(ownmutex_dbg,"Attempting ownMutex_Lock at wait(%p) by thread %x\n", handle, (unsigned int)pthread_self());
  fflush(ownmutex_dbg);
#endif
  if (timeout_ms >= 0) {
    struct timeval now;
    struct timespec timeout;
    int delta_s = timeout_ms / 1000;
    int delta_us = (timeout_ms % 1000) * 1000;
    gettimeofday(&now, NULL);

    timeout.tv_sec = now.tv_sec + delta_s;
    timeout.tv_nsec = (now.tv_usec+delta_us) * 1000;
    if (timeout.tv_nsec > 1000000000L) {
      timeout.tv_sec += 1;
      timeout.tv_nsec -= 1000000000L;
    }
    r = pthread_cond_timedwait(handle, mutex, &timeout);

    if (r == ETIMEDOUT) {
      status = ownStatusTimeout;
    } else if (r != 0) {
      status = ownStatusError;
    }

  } else {
    r = pthread_cond_wait(handle, mutex);
    if (r) status = ownStatusError;
  }
#ifdef OWNMUTEX_DEBUGGING
  assert(ownmutex_dbg != NULL);
  fprintf(ownmutex_dbg,"ownMutex_Unlock called at wait(%p) by thread %x\n", handle, (unsigned int)pthread_self());
  fflush(ownmutex_dbg);
#endif
  //printf("\n%lu Returned from wait %p\n", pthread_self(), handle);
#else
 #warning Unsupported platform for function 'ownCond_Wait'
#endif

  return status;
}
//******************************************************************************

ownStatus ownCond_Signal(ownCondHandle handle)
{
  ownStatus status = ownStatusOK;

#if defined( WINDOWS_OS )
  PulseEvent(handle);

#elif defined( LINUX_OS )
  //printf("\n%lu Signaled %p\n", pthread_self(), handle);
  pthread_cond_signal(handle);
#else
 #warning Unsupported platform for function 'ownCond_Signal'
#endif

  return status;
}
//******************************************************************************

ownStatus ownCond_Broadcast(ownCondHandle handle)
{
  ownStatus status = ownStatusOK;

#if defined( WINDOWS_OS )
  PulseEvent(handle);
#elif defined( LINUX_OS )
  //printf("\n%lu Broadcasted %p\n", pthread_self(), handle);
  pthread_cond_broadcast(handle);
#else
 #warning Unsupported platform for function 'ownCond_Broadcast'
#endif

  return status;
}
//******************************************************************************

ownThreadHandle ownThread_Create(void *routine, void *parameter)
{
  ownThreadHandle handle;
#if defined( WINDOWS_OS )
  DWORD ownThread_thread_Id;
  handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)routine,
                                  parameter, 0, &ownThread_thread_Id);

#elif defined( LINUX_OS )
  int thread_create_status;
  handle = malloc(sizeof(pthread_t));
  assert(handle != NULL);

  thread_create_status = pthread_create(handle, NULL, routine, parameter);
  if (thread_create_status) {
    perror("pthread_create(handle, NULL, routine, parameter)");
    abort();
  }

#else
 #warning Unsupported platform for function 'ownThread_Create'
#endif

  return handle;
}
//******************************************************************************

ownStatus ownThread_Join(ownThreadHandle handle, void *return_value)
{
  ownStatus status = ownStatusOK;

#if defined( WINDOWS_OS )
  WaitForSingleObject(handle, INFINITE);

#elif defined( LINUX_OS )
  int i;
  i = pthread_join(*handle, return_value);
  status = (i) ? ownStatusError : ownStatusOK;

  // After Join; Handle is invalidated.
  free(handle);

  if (i) { errno=i; perror("pthread_join"); }

#else
 #warning Unsupported platform for function 'ownThread_Join'
#endif

  return status;
}
//******************************************************************************

ownStatus ownThread_Signal(ownThreadHandle handle, int aSignal)
{
  ownStatus status = ownStatusOK;

#if defined( WINDOWS_OS )
# warning Not implemented in Windoze.

#elif defined( LINUX_OS )
  int i;
  i = pthread_kill(*handle, aSignal);
  status = (i) ? ownStatusError : ownStatusOK;

#else
# warning Unsupported platform for function 'ownThread_Signal'
#endif

  return status;
}
//******************************************************************************

ownStatus ownThread_Cancel(ownThreadHandle handle)
{
  ownStatus status = ownStatusOK;
  
#if defined( WINDOWS_OS )
#warning ownThread_Cancel not yet implemented on Windows! (Please fix me!)
  status = ownStatusError;
#elif defined( LINUX_OS )
  int r = pthread_cancel(*handle);
  if (r) status = ownStatusError;
#else
# warning Unsupported platform for function 'ownThread_Cancel'
#endif
  
  return status;
}
//******************************************************************************

ownStatus ownThread_TestCancel(void)
{
  ownStatus status = ownStatusOK;

#if defined( WINDOWS_OS )
  #warning ownThread_Testcancel not yet implemented on Windows! (Please fix me!)
  status = ownStatusError;
#elif defined( LINUX_OS )
  pthread_testcancel();
#else
# warning Unsupported platform for function 'ownThread_Testcancel'
#endif

  return status;
}
//******************************************************************************

ownStatus ownThread_SetCancelState(int state)
{
  ownStatus status = ownStatusOK;
  int r;

#if defined( WINDOWS_OS )
# warning ownThread_SetCancelState not yet implemented on Windows! (Please fix me!)
  status = ownStatusUnimplemented;
  r = r;
#elif defined( LINUX_OS )

  switch(state) {
  case ownThreadCancelStateEnable:
    r = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
    break;
  case ownThreadCancelStateDisable:
    r = pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
    break;
  default:
    printf("Invalid CancelState to ownThread_SetCancelState() = %d\n", state);
    abort();
    break;
  }
  status = r ? ownStatusError : ownStatusOK;

#else
# warning Unsupported platform for function 'ownThread_Testcancel'
#endif

  return status;
}
//******************************************************************************

ownStatus ownThread_SetCancelType(int type)
{
  ownStatus status = ownStatusOK;
  int r;

#if defined( WINDOWS_OS )
# warning ownThread_Testcancel not yet implemented on Windows! (Please fix me!)
  status = ownStatusUnimplemented;
  r = r;
#elif defined( LINUX_OS )

  switch(type) {
  case ownThreadCancelTypeDeferred:
    r = pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
    break;
  case ownThreadCancelTypeAsync:
    r = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
    break;
  default:
    printf("Invalid CancelType to ownThread_SetCancelType() = %d\n", type);
    abort();
    break;
  }
  status = r ? ownStatusError : ownStatusOK;
#else
# warning Unsupported platform for function 'ownThread_Testcancel'
#endif

  return status;
}
//******************************************************************************

void ownSched_Yield(void)
{
#if defined( WINDOWS_OS )
  Sleep(0);

#elif defined( LINUX_OS )
  sched_yield();

#else
# warning Unsupported platform for function 'ownSched_Yield'
#endif

}
//******************************************************************************

uint32_t ownHash_SimpleCRC(const unsigned char * aData,
                           const unsigned int aLen)
{
  uint32_t h = 0;
  uint32_t highorder = 0;
  unsigned int i;

  for(i=0;
      i < aLen;
      ++i) {
    highorder = h & 0xf8000000;    // extract high-order 5 bits from h
    h = h << 5;                    // shift h left by 5 bits
    h = h ^ (highorder >> 27);     // move the highorder 5 bits to the low-order
    h = h ^ aData[i];         // XOR h and ki
  }

  // h is the final value.
  return h;
}
//******************************************************************************

int ownCompare_Float(const float aA, const float aB, const float aAccuracy)
{
  int r;

  if (aA > aB + aAccuracy) r = -1;
  else if (aA < aB - aAccuracy) r = 1;
  else r = 0;

  return r;
}
//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************
//******************************************************************************
